#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */ 
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Adoor.Expression;
using Adoor.Expression.AST;
using Adoor.Object.Entity;
using Adoor.Object.Domain;

namespace Adoor.Object.Query
{
	/// <summary>
	/// Object that knows how to evaluate an <see cref="ObjectPath"/>.
	/// </summary>
	/// <remarks>
	/// To evaluate an <see cref="ObjectPath"/>, call <see cref="ObjectPath.EvaluateFrom(IObjectPathEvaluator)"/>. <see cref="ObjectPath.EvaluateFrom(IObjectPathEvaluator)"/>
	/// routes the call to the method corresponding to the actual runtime type which specialized descendants of the
	/// evaluator know how to evaluate (for the aficionados of design patterns the 
	/// <see cref="ObjectPath"/>/<see cref="IObjectPathEvaluator"/> couple follows the GOF's "visitor pattern").
	/// The result of the evaluation as well as potential extra parameters are generally exposed by the
	/// specialized evaluator.</remarks>
	public interface IObjectPathEvaluator
	{
		/// <summary>
		/// Evaluates the basic object operator Alias.
		/// The precise meaning of "evaluate" is defined by the class supporting the interface.
		/// </summary>
		/// <param name="a">The basic object operator Alias to evaluate.</param>
		void EvaluateScan(ScanPath a);

		/// <summary>
		/// Evaluates the basic object operator Join.
		/// The precise meaning of "evaluate" is defined by the class supporting the interface.
		/// </summary>
		/// <param name="j">The basic object operator Join to evaluate.</param>
		void EvaluateJoin(JoinPath j);

		/// <summary>
		/// Evaluates the basic object operator Cross.
		/// The precise meaning of "evaluate" is defined by the class supporting the interface.
		/// </summary>
		/// <param name="j">The basic object operator Cross to evaluate.</param>
		void EvaluateCross(CrossPath j);

		/// <summary>
		/// Evaluates the basic object operator Cross.
		/// The precise meaning of "evaluate" is defined by the class supporting the interface.
		/// </summary>
		/// <param name="j">The basic object operator Cross to evaluate.</param>
		void EvaluateCrossQuery(CrossPathQuery j);


		/// <summary>
		/// Evaluates the basic object operator Filter
		/// The precise meaning of "evaluate" is defined by the class supporting the interface.
		/// </summary>
		/// <param name="f">The basic object operator Filter to evaluate.</param>
		void EvaluateFilter(FilterPath f);

		/// <summary>
		/// Evaluates the basic object operator Intersect
		/// The precise meaning of "evaluate" is defined by the class supporting the interface.
		/// </summary>
		/// <param name="i">The basic object operator Intersect to evaluate.</param>
		void EvaluateIntersect(IntersectPath i);

		void EvaluateScanQuery(ScanPathQuery s);

		bool InOuterJoin { get; set; }
	}

	/// <summary>
	/// The from and where clauses of an <see cref="ObjectQuery"/>
	/// </summary>
	public abstract class ObjectPath
	{
        protected ObjectPath(IObjectResolver objectResolver)
        {
            this.objectResolver = objectResolver;
        }

		/// <summary>
		/// The objectResolver over which this path is defined.
		/// </summary>
        private IObjectResolver objectResolver;
		public IObjectResolver ObjectResolver 
        {
            get
            {
                return objectResolver;
            }
            set
            {
                this.objectResolver = value;
            }
        }

		/// <summary>
		/// List of roles used by this expression. A correlation is a valid start point
		/// for a join.
		/// </summary>
		public abstract IDictionary<string, Correlation>/*Correlation*/ Correlations { get; }

		/// <summary>
		/// Evaluate the current query through an evaluator. The current query is decomposed
		/// into basic object operators which are then evaluated by the evaluator by calling the 
		/// corresponding methods.
		/// </summary>
		/// <param name="e">The object evaluator to evaluate the current query through.</param>
		public abstract void EvaluateFrom(IObjectPathEvaluator e);

		/// <summary>
		/// Perform a join on a reference.
		/// </summary>
		/// <param name="correlation">The start point of the join</param>
		/// <param name="isOuterJoin">The mode of the join (true if outer, false if inner)</param>
		/// <param name="role">The name of the reference to join along</param>
		/// <param name="alias">The name of the result join as a new correlation</param>
		public virtual ObjectPath Join(bool isOuterJoin, string correlation, string role, string alias, string referenceFilter)
		{
			return new JoinPath(this, correlation, isOuterJoin, role, alias, referenceFilter, ObjectResolver);
		}

		/// <summary>
		/// Perform a series of joins according to the following syntax
		/// path_list ::= { path "," }+
		/// path ::= (ref_path | list_path)
		/// ref_path ::= correlation:id "." role:id ["?"] "as" alias:id
		/// list_path ::= alias:id "in" correlation:id "." role:id ["?"]
		/// 
		/// The variant syntaxes have the following semantics:
		/// correlation.role => inner join 
		/// correlation.role? => outer join
		/// </summary>
		public virtual ObjectPath Join(string text)
		{
			Scanner scanner = new Scanner(text);
			scanner.Scan();
			return ObjectPath.Parse(scanner, this.objectResolver, this);
		}

		public virtual ObjectPath Cross(string name)
		{
			return Cross(name, name, null, false);
		}

		public virtual ObjectPath Cross(string name, string alias, ExpressionNode conditionNode, bool isOuterJoin)
		{
			return Cross(this.ObjectResolver.GetPath(name), alias, conditionNode, isOuterJoin);
		}

		public virtual ObjectPath Cross(ObjectPath source2, string alias, ExpressionNode conditionNode, bool isOuterJoin)
		{
            return new CrossPath(this, source2, alias, conditionNode, isOuterJoin, ObjectResolver);
		}

		public virtual ObjectPath CrossQuery(ObjectQuery query, string alias, ExpressionNode condition, bool isOuterJoin)
		{
            return new CrossPathQuery(this, query, alias, condition, isOuterJoin, ObjectResolver);
		}

		/// <summary>
		/// Returns a new query which is a filter on the current query.
		/// </summary>
		/// <param name="condition">A condition that is applied on each row returned by the current
		/// query.
		/// The syntax of the condition is similar to that of <c>DataView.Filter</c>.
		/// The condition may contain "?" placeholders whose values are passed in the "args" argument.
		/// The condition may also contain the "{0}" format specifier which is replaced with the 
		/// alias of the current query.</param>
		/// <remarks>Do not forget to escape "{" and "}" characters in the condition (though these characters
		/// should only appear in strings and be passed as args).</remarks>
		public virtual ObjectPath Where(string condition, ExpressionParser parser, IDictionary<string, Correlation> additionalPath)
		{
			return this.Where(parser.ParseCondition(condition), additionalPath);
		}

		public virtual ObjectPath Where(string condition)
		{
			return this.Where(condition, new ExpressionParser(), null);
		}

		public virtual ObjectPath Where(ExpressionNode condition, IDictionary<string, Correlation> additionalPath)
		{
			if(additionalPath != null)
			{
				foreach(KeyValuePair<string, Correlation> entry in additionalPath)
					this.Correlations.Add(entry.Key, entry.Value);
			}
			// TODO: this is just to verify that the conditions are well-formed (and that our parser is correct!)
			// TODO: this will be replaced by a more thorough analysis in the future
			ExpressionValidator.Validate(this, condition); // TODO: still useful ?

            return new FilterPath(this, condition, ObjectResolver);
		}

		public virtual ObjectPath Where(ExpressionNode condition)
		{
			return Where(condition, null);
		}

		public ObjectPath Intersect(string correlation1, ObjectPath source2, string correlation2)
		{
            return new IntersectPath(this, correlation1, source2, correlation2, ObjectResolver);
		}

		public ObjectPath Intersect(ObjectPath source2, string correlation2)
		{
			if (this.Correlations.Count != 1)
				throw new Exception("ambiguity in Intersect expression: choose a correlation name for source #1");
			Correlation[] s = new Correlation[1];
			this.Correlations.Values.CopyTo(s, 0);
			return Intersect(s[0].Name, source2, correlation2);
		}

		public ObjectPath Intersect(string correlation1, ObjectPath source2)
		{
			if (source2.Correlations.Count != 1)
				throw new Exception("ambiguity in Intersect expression: choose a correlation name for source #2");
			Correlation[] s = new Correlation[1];
			source2.Correlations.Values.CopyTo(s, 0);
			return Intersect(correlation1, source2, s[0].Name);
		}

		public ObjectPath Intersect(ObjectPath source2)
		{
			if (this.Correlations.Count != 1)
				throw new Exception("ambiguity in Intersect expression: choose a correlation name for source #1");
			Correlation[] s = new Correlation[1];
			this.Correlations.Values.CopyTo(s, 0);
			return Intersect(s[0].Name, source2);
		}

		public ObjectPath Minus(string correlation1, ObjectPath source2, string correlation2)
		{
			if (correlation1 == null)
				throw new Exception("ambiguity in Minus expression: choose a correlation name for source #1");
			if (correlation2 == null)
				throw new Exception("ambiguity in Minus expression: choose a correlation name for source #2");
            return new IntersectPath(this, correlation1, source2, correlation2, true, ObjectResolver);
		}

		public ObjectPath Minus(ObjectPath source2, string correlation2)
		{
			if (this.Correlations.Count == 1)
			{
				Correlation[] s = new Correlation[1];
				this.Correlations.Values.CopyTo(s, 0);
				return Minus(s[0].Name, source2, correlation2);
			}
			else 
			{
				Correlation s2 = (Correlation)source2.Correlations[correlation2];
				if (s2 == null)
					throw new Exception("correlation " + correlation2 + " not found");
				string correlation1 = null;
				foreach(Correlation s1 in this.Correlations.Values)
				{
					if (s2.EntityData.EntityType.IsAssignableFrom(s1.EntityData.EntityType))
					{
						if (correlation1 != null)
							throw new Exception("ambiguity in Minus expression: choose a correlation for source #1");
						correlation1 = s1.Name;
					}
				}
				return Minus(correlation1, source2, correlation2);
			}
		}

		public ObjectPath Minus(string correlation1, ObjectPath source2)
		{
			if (source2.Correlations.Count == 1)
			{
				Correlation[] s = new Correlation[1];
				source2.Correlations.Values.CopyTo(s, 0);
				return Minus(correlation1, source2, s[0].Name);
			}
			else
			{
				Correlation s1 = (Correlation)this.Correlations[correlation1];
				if (s1 == null)
					throw new Exception("correlation name " + correlation1 + " not found");

				string correlation2 = null;
				foreach(Correlation s2 in source2.Correlations.Values)
				{
					if (s2.EntityData.EntityType.IsAssignableFrom(s1.EntityData.EntityType))
					{
						if (correlation2 != null)
							throw new Exception("ambiguity in Minus expression: choose a correlation name for source #2");
						correlation2 = s2.Name;
					}
				}
				return Minus(correlation1, source2, correlation2);
			}
		}

		public ObjectPath Minus(ObjectPath source2)
		{
			if (this.Correlations.Count == 1)
			{
				Correlation[] s = new Correlation[1];
				this.Correlations.Values.CopyTo(s, 0);
				return Minus(s[0].Name, source2);
			}
			else if (source2.Correlations.Count == 1)
			{
				Correlation[] s = new Correlation[1];
				source2.Correlations.Values.CopyTo(s, 0);
				return Minus(source2, s[0].Name);
			}
			else // we intersect this.Correlations with source2.Correlations, but is it desirable...
				throw new Exception("ambiguity in Minus expression: choose a correlation name for source #1");
		}

		/*
		 * path_list ::= {path "," ...}*
		 * path ::= (cross_path | ref_path | list_path)
		 * cross_path ::= type:id [["as"] alias:id]
		 * ref_path ::= correlation:id "." role:id ["?"] [["as"] alias:id] 
		 * list_path ::= alias:id "in" correlation:id "." role:id ["?"]
		 */		
		public static ObjectPath Parse(IObjectResolver objectResolver, string s)
		{
			if(objectResolver == null) throw new ArgumentNullException("objectResolver");
			Scanner scanner = new Scanner(s);
			try
			{
				scanner.Scan();
				ObjectPath result = ObjectPath.Parse(objectResolver, scanner);
				if (scanner.Token != ScannerToken.End)
					throw new Exception("syntax error");
				return result;
			}
			catch(Exception e)
			{
				throw new Exception(e.Message + " (pos = " + scanner.Pos.ToString() + ")", e);
			}
		}

		public static ObjectPath Parse(IObjectResolver objectResolver, Scanner scanner)
		{
			return Parse(objectResolver, scanner, new ExpressionParser());
		}

        public static ObjectPath Parse(IObjectResolver objectResolver, Scanner scanner, ExpressionParser parser)
		{
			return Parse(objectResolver, scanner, parser, null);
		}

		public static ObjectPath Parse(IObjectResolver objectResolver, Scanner scanner, ExpressionParser parser, ObjectPath additionalPath)
		{
			if(objectResolver == null) throw new ArgumentNullException("objectResolver");
			ObjectPath result = null;
			if(additionalPath == null)
				result = ObjectPath.ParsePathList(scanner, objectResolver, null, parser);
			else
				result = ObjectPath.ParsePathList(scanner, objectResolver, null, additionalPath.Correlations, parser);
			parser.Tag = result;

			if (scanner.Token == ScannerToken.Where)
			{
				object oldtag = scanner.Tag;
				try
				{
					scanner.Tag = objectResolver.ManagedDomains;
					scanner.Scan();
					ExpressionNode en = parser.ParseSearchCondition(scanner);
					if(additionalPath != null)
						result = result.Where(en, additionalPath.Correlations);
					else
						result = result.Where(en);
					parser.Tag = result;
				}
				finally
				{
					scanner.Tag = oldtag;
				}
			}
			return result;
		}

		private static ObjectPath Parse(Scanner scanner, IObjectResolver objectResolver, ObjectPath current)
		{
			return Parse(scanner, objectResolver, current, new ExpressionParser());
		}

		private static ObjectPath Parse(Scanner scanner, IObjectResolver objectResolver, ObjectPath current, ExpressionParser parser)
		{
			if(objectResolver == null) throw new ArgumentNullException("objectResolver");
			try
			{
				ObjectPath result = ParsePathList(scanner, objectResolver, current, parser);
				if (scanner.Token != ScannerToken.End)
					throw new Exception("syntax error");
				return result;
			}
			catch(Exception e)
			{
				throw new Exception(e.Message + " (pos = " + scanner.Pos.ToString() + ")", e);
			}
		}

		private static ObjectPath ParsePathList(Scanner scanner, IObjectResolver objectResolver, ObjectPath current, ExpressionParser parser )
		{
			return ParsePathList(scanner, objectResolver, current, null, parser);
		}

		private static ObjectPath ParsePathList(Scanner scanner, IObjectResolver objectResolver, ObjectPath current, IDictionary<string, Correlation> additionalCorrelations, ExpressionParser parser)
		{
			if(objectResolver == null) throw new ArgumentNullException("objectResolver");
			
			ObjectPath result = current;
			while (scanner.Token == ScannerToken.Name 
				|| scanner.Token == ScannerToken.LeftParen
				|| scanner.Token == ScannerToken.Inner
				|| scanner.Token == ScannerToken.Left)
			{
				bool addToExcludedList = false;
				if (scanner.Token == ScannerToken.LeftParen)
				{
					scanner.Scan();
					if(scanner.Token == ScannerToken.Select)
					{
						result = ParseJoinQuery(scanner, result, objectResolver, false, parser);

						goto continuePathList;
					}
					else
						addToExcludedList = true;
				}

				result = ParsePath(scanner, objectResolver, result, addToExcludedList, parser);
				if (addToExcludedList)
				{
					if (scanner.Token != ScannerToken.RightParen)
						throw new Exception("')' expected");
					scanner.Scan();
				}

			continuePathList:
				if (scanner.Token == ScannerToken.Inner
					|| scanner.Token == ScannerToken.Left)
					continue;

				if (scanner.Token != ScannerToken.Comma)
					break;
				scanner.Scan();
			}

			
			return result;
		}

		private static ObjectPath ParseJoinQuery(Scanner scanner, ObjectPath current, IObjectResolver objectResolver, bool isOuterJoin, ExpressionParser parser)
		{
			ObjectQuery oq = new ObjectQuery(objectResolver, "");
			oq.Parse(scanner, ScannerToken.RightParen);

			scanner.Scan();
			if(scanner.Token == ScannerToken.QuestionMark)
			{
				isOuterJoin = true;
				scanner.Scan();
			}
			
			if(scanner.Token == ScannerToken.As)
				scanner.Scan();

			if(scanner.Token != ScannerToken.Name)
				throw new Exception("alias expected");
			
			string alias = scanner.GetString();
			scanner.Scan();

			
			if(current == null)
                return new ScanPathQuery(oq, alias, objectResolver);

			ExpressionNode en = null;
			
			if(scanner.Token != ScannerToken.On)
				throw new Exception(" 'on' expected");
			scanner.Scan();
			
			object oldtag = scanner.Tag;
			try
			{
				//scanner.Tag = objectResolver.objectResolverGroup;
				en = parser.ParseSearchCondition(scanner);				
			}
			finally
			{
				scanner.Tag = oldtag;
			}
			
			return current.CrossQuery(oq, alias, en, isOuterJoin);
		}

		private static ObjectPath ParsePath(Scanner scanner, IObjectResolver objectResolver, ObjectPath current, bool addToExcludedList, ExpressionParser parser)
		{
			bool isOuter = false;
			if(scanner.Token == ScannerToken.Left)
			{
				isOuter = true;
				scanner.Scan();
				if(scanner.Token == ScannerToken.Outer)
				{
					scanner.Scan();
				}

				if(scanner.Token != ScannerToken.Join)
				{
					throw new Exception("Join expected");
				}
				scanner.Scan();
				
			}

			if(scanner.Token == ScannerToken.Inner)
			{
				isOuter = false;
				scanner.Scan();
				if(scanner.Token != ScannerToken.Join)
				{
					throw new Exception("Join expected");
				}
				scanner.Scan();
			}

			if (scanner.Token == ScannerToken.LeftParen)
			{
				scanner.Scan();
				if(scanner.Token == ScannerToken.Select)
				{
					return ParseJoinQuery(scanner, current, objectResolver, isOuter, parser);
				}
				else
					throw new Exception("select expected");
			}

			if(objectResolver == null) throw new ArgumentNullException("objectResolver");
			if (scanner.Token != ScannerToken.Name)
				throw new Exception("name expected");
			string id = scanner.GetString();
			if (id.StartsWith("[") && id.EndsWith("]"))
				id = id.Substring(1, id.Length - 2);
			scanner.Scan();


			if (scanner.Token == ScannerToken.Period)
			{
				scanner.Scan();
				return ParseRefPath(scanner, current, id, false, addToExcludedList);
			}
			if (scanner.Token == ScannerToken.In)
			{
				scanner.Scan();
				return ParseListPath(scanner, current, id, addToExcludedList);
			}

			string alias = id;
			if (scanner.Token == ScannerToken.As)
				scanner.Scan(); // 'as' is optional
			if (scanner.Token == ScannerToken.Name)
			{
				alias = scanner.GetString();
				if (alias.StartsWith("[") && alias.EndsWith("]"))
					alias = alias.Substring(1, alias.Length - 2);
				scanner.Scan();
			}
			
			ObjectPath result = ParseCrossPath(current, objectResolver, id, alias, addToExcludedList, null, isOuter);
			if(result is CrossPath)
			{
				CrossPath cp = (CrossPath) result;

					ExpressionNode onCondition = null;
				if (scanner.Token == ScannerToken.On)
				{
					scanner.Tag = objectResolver;
					object oldtag = scanner.Tag;
					try
					{
						//scanner.Tag = objectResolver.objectResolverGroup;
						scanner.Scan();				
						parser.Tag = cp;
						onCondition = parser.ParseSearchCondition(scanner);
					}
					finally
					{
						scanner.Tag = oldtag;
					}
				}

				cp.JoinCondition = onCondition;
			}
			return result;
		}

		private static ObjectPath ParseRefPath(Scanner scanner, ObjectPath current, string id1, bool isOuterJoin, bool addToExcludedList)
		{
			if (scanner.Token != ScannerToken.Name)
				throw new Exception("name expected");
			string id2 = scanner.GetString();
			if (id2.StartsWith("[") && id2.EndsWith("]"))
				id2 = id2.Substring(1, id2.Length - 2);
			scanner.Scan();
			if (!isOuterJoin)
			{
				if (scanner.Token == ScannerToken.QuestionMark)
				{
					isOuterJoin = true;
					scanner.Scan();
				}
			}

			string id3 = id2;
			string referenceFilter = null;
			if (scanner.Token == ScannerToken.Exclamation)
			{
				scanner.Scan();
				if(scanner.Token != ScannerToken.Name)
					throw new Exception("name missing");
				referenceFilter = scanner.GetString();
				scanner.Scan();
				id3 = referenceFilter;
			}

			if (scanner.Token == ScannerToken.As)
				scanner.Scan(); // 'as' is optional
			
			if (scanner.Token == ScannerToken.Name)
			{
				id3 = scanner.GetString();
				if (id3.StartsWith("[") && id3.EndsWith("]"))
					id3 = id3.Substring(1, id3.Length - 2);
				scanner.Scan();
			}
			// TODO: check it's a single reference
			if (current == null)
				throw new Exception("invalid path " + id1 + "." + id2);
			ObjectPath result = current.Join(isOuterJoin, id1, id2, id3, referenceFilter);
			if (addToExcludedList)
				AddExcluded(result, id3);
			return result;
		}

		private static ObjectPath ParseCrossPath(ObjectPath current, IObjectResolver objectResolver, string id, 
			string alias, bool addToExcludedList, 
			ExpressionNode conditionNode, bool isOuter)
		{
			if(objectResolver == null) throw new ArgumentNullException("objectResolver");
			ObjectPath result;
			if (current == null)
			{
				result = objectResolver.GetPath(id, alias);
			}
			else
			{
				result = current.Cross(id, alias, conditionNode, isOuter);
			}
			if (addToExcludedList)
				AddExcluded(result, alias);
			return result;
		}

		private static ObjectPath ParseListPath(Scanner scanner, ObjectPath current, string id1, bool addToExcludedList)
		{
			bool isOuterJoin;
			if (scanner.Token != ScannerToken.Name)
				throw new Exception("name expected");
			string id2 = scanner.GetString();
			scanner.Scan();
			if (scanner.Token != ScannerToken.Period)
				throw new Exception("excepting '.'");
			scanner.Scan();
			isOuterJoin = false;
			if (scanner.Token != ScannerToken.Name)
				throw new Exception("name expected");
			string id3 = scanner.GetString();
			scanner.Scan();
			if (!isOuterJoin)
			{
				if (scanner.Token == ScannerToken.QuestionMark)
				{
					scanner.Scan();
					isOuterJoin = true;
				}
			}

			string referenceFilter = null;
			if (scanner.Token == ScannerToken.Exclamation)
			{
				scanner.Scan();
				if(scanner.Token != ScannerToken.Name)
					throw new Exception("name missing");
				referenceFilter = scanner.GetString();
			}
			// TODO: check it's a multiple reference
			if (current == null)
				throw new Exception("invalid source " + id2 + "." + id3);
			ObjectPath result = current.Join(isOuterJoin, id2, id3, id1, referenceFilter);
			if (addToExcludedList)
				AddExcluded(result, id1);
			return result;
		}

		private static void AddExcluded(ObjectPath result, string correlation)
		{
			Correlation c = (Correlation)result.Correlations[correlation];
			c.Excluded = true;
		}
	}

	/// <summary>
	/// The from clause of an <see cref="ObjectQuery"/>.
	/// </summary>
	public class ScanPath: ObjectPath
	{
		public ScanPath(EntityData entityData, string alias, IObjectResolver objectResolver) : base(objectResolver)
		{
			this.entityData = entityData;
			this.alias = alias;
			this.correlations[alias] = new Correlation(alias, this.entityData);
		}

		public EntityData EntityData
		{
			get { return this.entityData; }
		}

		public string Alias
		{
			get { return this.alias; }
			set
			{ 
				Correlation cor = (Correlation) this.correlations[alias];
				this.correlations.Remove(alias);
				this.alias = value; 
				cor.Name = this.alias;
				this.correlations[alias] =  cor;
			}
		}

		public override IDictionary<String, Correlation> Correlations
		{
			get { return this.correlations; }
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
			ObjectPath innerQuery = this;
			foreach(KeyValuePair<string, Correlation> entry in Correlations)
			{
				Correlation c = entry.Value;
				string alias = entry.Key;

				foreach(string s in c.NeededInverseRelations)
				{
					ReferenceData rd = null;
					if(Correlations.ContainsKey(s))
					{
						Correlation subc = Correlations[s];
						rd = subc.EntityData.ReferenceInfos[Alias];
					}
					if(rd == null)
					{
						Adoor.Object.Domain.EntityData ed = ObjectResolver.FindEntityData(s);
						if(ed == null)
							throw new Exception("unknown reference " + s);
						rd = ed.ReferenceInfos[Alias];
						Correlation co = new Correlation(s, c, rd, true);
						//c.ReversedLinkedCorrelations.Add(s, co);
						c.ReversedLinkedCorrelations.Add(rd, co);
					}
					if(rd == null)
					{
						throw new Exception("unknown reference " + s +"." + Alias);
					}

					ExpressionName en = new ExpressionName(Alias, s);
					c.NeededRelations.Add(rd, en);
				}
				c.NeededInverseRelations.Clear();

				foreach(KeyValuePair<ReferenceData, ExpressionName> entry2 in c.NeededRelations)
				{
					ReferenceData refData = entry2.Key;
					ExpressionName en = entry2.Value;
					if(!refData.IsSingleReference)
					{
                        JoinPath jp = new JoinPath(c, innerQuery, refData, en.Value, e.InOuterJoin, ObjectResolver);
						innerQuery = jp;
					}
					else
					{
                        JoinPath jp = new JoinPath(c, innerQuery, refData, en.Value, e.InOuterJoin, ObjectResolver);
						innerQuery = jp;
					}
				}
			}
			
			if(innerQuery is ScanPath)
				e.EvaluateScan( (ScanPath) innerQuery);
			else
				innerQuery.EvaluateFrom(e);
		}


		private EntityData entityData;
		private string alias;
        private IDictionary<string, Correlation>/*Correlation*/ correlations = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
	}

	

	/// <summary>
	/// Join clause in an <see cref="ObjectPath"/>.
	/// </summary>
	public class JoinPath: ObjectPath
	{
		public JoinPath(ObjectPath innerQuery, string correlation, bool isOuterJoin, string role, string alias, string referenceFilter
            , IObjectResolver objectResolver) : base(objectResolver)
		{
			this.innerQuery = innerQuery;
			this.alias = alias;
			foreach(string key in innerQuery.Correlations.Keys)
				this.correlations[key] = this.innerQuery.Correlations[key];
			this.correlation = (Correlation)this.correlations[correlation];
			if (this.correlation == null)
				throw new Exception("correlation \"" + correlation + "\" not found");

            if(!this.correlation.EntityData.ReferenceInfos.TryGetValue(role, out this.referenceData))
				throw new Exception("Role \"" + role + "\" not found for correlation \"" + correlation + "\" not found");

			this.referenceFilter = referenceFilter;
			if(ReferenceFilter != null)
			{
				EntityData childData = null;
				childData = this.ReferenceData.TargetData.FindChild(this.ReferenceFilter.ToLower());

				if(childData == null)
					throw new Exception("child \"" + ReferenceFilter + "\" not found for role " + role);
				this.referenceData = this.ReferenceData.Clone(childData);					
			}
			

			if (this.correlation.IsOuterJoin)  // when some join in the chain is outer, every successor is outer (otherwise, it is meaningless)
				this.isOuterJoin = true;
			else
				this.isOuterJoin = isOuterJoin;
			correlations[alias] = new Correlation(alias, this.correlation, this.referenceData, this.isOuterJoin);
		}

		public JoinPath(Correlation c, ObjectPath innerQuery, ReferenceData refData, string alias, bool isOuterJoin
            , IObjectResolver objectResolver)
            : base(objectResolver)
		{
			partial = true;
			this.correlation = c;
			this.referenceData = refData;
			this.isOuterJoin = false;
			this.innerQuery = innerQuery;
			this.alias = alias;
			this.isOuterJoin = isOuterJoin;
		}

		private bool partial = false;

		public override IDictionary<string, Correlation>/*Correlation*/ Correlations
		{
			get { return this.correlations; }
		}

		public string Role
		{
			get { return this.referenceData.TargetRole; }
		}

		public ObjectPath InnerQuery
		{
			get { return this.innerQuery; }
		}

		public bool IsOuterJoin
		{
			get { return this.isOuterJoin; } 
		}

		public Correlation Correlation
		{
			get { return this.correlation; }
		}

		public string Alias
		{
			get { return this.alias; }
		}

		public string ReferenceFilter
		{
			get{ return this.referenceFilter; }
		}

		public ReferenceData ReferenceData
		{
			get { return this.referenceData; }
		}

		public bool Partial
		{
			get { return this.partial; }
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
//			foreach(DictionaryEntry entry2 in Correlation.NeededRelations)
//			{
//				ReferenceData refData = entry2.Key as ReferenceData;
//				ExpressionName en = entry2.Value as ExpressionName;
//				if(!refData.IsSingleReference)
//				{
//					JoinPath jp = new JoinPath(Correlation, innerQuery, refData, en.Value, IsOuterJoin);
//					innerQuery = jp;
//				}
//			}

			Correlation.NeededRelations.Remove(ReferenceData);
			e.EvaluateJoin(this);
		}

		private ObjectPath innerQuery;
		private bool isOuterJoin;
		private string alias;
        private IDictionary<string, Correlation>/*Correlation*/ correlations = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
		private Correlation correlation;
		private ReferenceData referenceData;
		private string referenceFilter;
	}

	/// <summary>
	/// Cartesian product in an <see cref="ObjectPath"/>.
	/// </summary>
	public class CrossPath: ObjectPath
	{
		public CrossPath(ObjectPath source1, ObjectPath source2, string alias, ExpressionNode conditionNode, bool isOuterJoin
            , IObjectResolver objectResolver)
            : base(objectResolver)
		{
//			if (source1.objectResolver != source2.objectResolver)
//				throw new Exception("objectResolver mismatch");
//			if (source2.Correlations.Count != 1)
//				throw new Exception("invalid cartesian product"); // temp exception
			this.source1 = source1;
			this.source2 = source2;
			this.alias = alias;
			this.join = conditionNode;
			this.isOuterJoin = isOuterJoin;
			if(this.join == null)
			{
				this.join = new ExpressionBinary( ScannerToken.Eq, new ExpressionPrimary(ScannerToken.Integer, "1"), new ExpressionPrimary(ScannerToken.Integer, "1"));
			}
			
			foreach (string key in source1.Correlations.Keys)
				this.correlations[key] = this.source1.Correlations[key];
			foreach (string key in source2.Correlations.Keys)
			{
//				Correlation c = (Correlation)this.source2.Correlations[key];
//				Correlation c2 = new Correlation(alias, c.EntityData);
//				this.correlations[alias] = c2;
				this.correlations[alias] = this.source2.Correlations[key];
			}

			if(conditionNode != null)
				ExpressionValidator.Validate(this, conditionNode);
		}

		public override IDictionary<string, Correlation>/*Correlation*/ Correlations
		{
			get { return this.correlations; }
		}

		public ObjectPath Source1
		{
			get { return this.source1; }
		}

		public ObjectPath Source2
		{
			get { return this.source2; }
		}

		public string Alias
		{
			get { return this.alias; }
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
//			ObjectPath innerQuery = this;
//			foreach(Correlation correlation in Correlations.Values)
//			{
//				foreach(DictionaryEntry entry2 in correlation.NeededRelations)
//				{
//					ReferenceData refData = entry2.Key as ReferenceData;
//					ExpressionName en = entry2.Value as ExpressionName;
//					if(!refData.IsSingleReference)
//					{
//						JoinPath jp = new JoinPath(correlation, innerQuery, refData, en.Value, IsOuterJoin);
//						innerQuery = jp;
//					}
//				}
//			}

			//if(innerQuery is CrossPath)
			//e.EvaluateCross( (CrossPath) innerQuery);
//			else
//				innerQuery.EvaluateFrom(e);
			e.EvaluateCross( this);
		}

		public bool IsOuterJoin
		{
			get { return this.isOuterJoin; }
		}

		public ExpressionNode JoinCondition
		{
			get { return this.join; }			
			set 
			{ 
				this.join = value; 
				if(this.join != null)
					ExpressionValidator.Validate(this, this.join);
			}
		}

		private ObjectPath source1;
		private ObjectPath source2;
		private string alias;
		private ExpressionNode join;
		private bool isOuterJoin;
        private IDictionary<string, Correlation>/*Correlation*/ correlations = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
	}

	/// <summary>
	/// Cartesian product in an <see cref="ObjectPath"/>.
	/// </summary>
	public class CrossPathQuery: ObjectPath
	{
		public CrossPathQuery(ObjectPath source1, ObjectQuery source2Query, string alias, ExpressionNode join, bool isOuterJoin
            , IObjectResolver objectResolver)
            : base(objectResolver)
		{
			//			if (source1.objectResolver != source2.objectResolver)
			//				throw new Exception("objectResolver mismatch");
			//			if (source2.Correlations.Count != 1)
			//				throw new Exception("invalid cartesian product"); // temp exception
			this.source1 = source1;
			this.source2Query = source2Query;
			this.alias = alias;
			this.isOuterJoin = isOuterJoin;
			this.join = join;
			foreach (string key in source1.Correlations.Keys)
				this.correlations[key] = this.source1.Correlations[key];
			
			Correlation c2 = new Correlation(alias, source2Query.VirtualEntityData);
			this.correlations[alias] = c2;

			ExpressionValidator.Validate(this, join);
		}

		public override IDictionary<string, Correlation>/*Correlation*/ Correlations
		{
			get { return this.correlations; }
		}

		public ObjectPath Source1
		{
			get { return this.source1; }
		}

		public ObjectQuery Source2Query
		{
			get { return this.source2Query; }
		}

		public string Alias
		{
			get { return this.alias; }
		}

		public bool IsOuterJoin
		{
			get { return this.isOuterJoin; }
		}

		public ExpressionNode JoinCondition
		{
			get { return this.join; }			
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
			e.EvaluateCrossQuery( this);
		}

		private ObjectPath source1;
		private ObjectQuery source2Query;
		private string alias;
		private ExpressionNode join;
		private bool isOuterJoin;
        private IDictionary<string, Correlation>/*Correlation*/ correlations = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
	}

	/// <summary>
	/// The from clause of an <see cref="ObjectQuery"/>.
	/// </summary>
	public class ScanPathQuery: ObjectPath
	{
		public ScanPathQuery(ObjectQuery scanQuery, string alias
            , IObjectResolver objectResolver) : base(objectResolver)
		{
			this.alias = alias;
			this.scanQuery = scanQuery;
			
			Correlation c = new Correlation(alias, scanQuery.VirtualEntityData);
			this.correlations[alias] = c;
		}
		
		public override IDictionary<string, Correlation> Correlations
		{
			get { return this.correlations; }
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
			e.EvaluateScanQuery( this);			
		}

		public string Alias
		{
			get{ return alias; }
			set{ alias = value; }
		}

		public ObjectQuery ScanQuery
		{
			get{ return scanQuery; }
			set{ scanQuery = value; }
		}

		private string alias; 
		private ObjectQuery scanQuery;
        private IDictionary<string, Correlation>/*Correlation*/ correlations = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
	}

	/// <summary>
	/// Where clause in an <see cref="ObjectPath"/>.
	/// </summary>
	public class FilterPath: ObjectPath
	{
		public FilterPath(ObjectPath innerQuery, ExpressionNode condition
            , IObjectResolver objectResolver)
            : base(objectResolver)
		{
			this.innerQuery = innerQuery;
			this.condition = condition;
		}

		public override IDictionary<string, Correlation>/*Correlation*/ Correlations
		{
			get { return this.innerQuery.Correlations; }
		}

		public ObjectPath InnerQuery
		{
			get { return this.innerQuery; }
		}

		public ExpressionNode Condition
		{
			get { return this.condition; }
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
			e.EvaluateFilter(this);
		}

		private ObjectPath innerQuery;
		private ExpressionNode condition;
	}

	/// <summary>
	/// "Where in" or "where not in" clause in an <see cref="ObjectPath"/>.
	/// </summary>
	public class IntersectPath: ObjectPath
	{
		public IntersectPath(ObjectPath source1, string correlation1, ObjectPath source2, string correlation2
            , IObjectResolver objectResolver)
            : base(objectResolver)
		{
//			if (source1.objectResolver != source2.objectResolver)
//				throw new Exception("objectResolver mismatch");
			this.source1 = source1;
			this.correlation1 = correlation1;
			this.source2 = source2;
			this.correlation2 = correlation2;
		}

		public IntersectPath(ObjectPath source1, string correlation1, ObjectPath source2, string correlation2, bool isMinus
            , IObjectResolver objectResolver)
            : base(objectResolver)
		{
			this.source1 = source1;
			this.correlation1 = correlation1;
			this.source2 = source2;
			this.correlation2 = correlation2;
			this.isMinus = isMinus;
		}

		public override IDictionary<string, Correlation>/*Correlation*/ Correlations
		{
			get { return this.source1.Correlations; }
		}

		public override void EvaluateFrom(IObjectPathEvaluator e)
		{
			e.EvaluateIntersect(this);
		}

		public ObjectPath Source1
		{
			get { return this.source1; }
		}

		public string Correlation1
		{ 
			get { return this.correlation1; }
		}

		public ObjectPath Source2
		{ 
			get { return this.source2; }
		}

		public string Correlation2
		{
			get { return this.correlation2; }
		}

		public bool IsMinus
		{
			get { return this.isMinus; }
		}

		ObjectPath source1;
		string correlation1;
		ObjectPath source2;
		string correlation2;
		bool isMinus;
	}

	public class ExpressionValidator: ExpressionNavigator
	{
		public static void Validate(ObjectPath path, ExpressionNode en)
		{
			ExpressionValidator ev = new ExpressionValidator(path);
			en.Accept(ev);
		}

		private ObjectPath path;
		private ExpressionValidator(ObjectPath path)
		{
			this.path = path;
		}

		public override void VisitName(ExpressionName n)
		{
			if (n.Qualifier == "") 
			{					
				string name = n.Value;
					
				if (!name.StartsWith("@") && !this.path.Correlations.ContainsKey(name))
					//throw new Exception("Correlation " + n.Value.Substring(1) + " not found");
					throw new Exception("Illegal use of unqualified name " + n.Value + ": all names should be qualified");
				return;
				// TODO: deduce n.Qualifier. Is it desirable ?
				//				foreach(Correlation s in this.query.Correlations)
				//				{
				//					if (s.Mapping.PropertyMappings.Has(n.Value))
				//					{
				//						if (n.Qualifier != "")
				//							throw new Exception("property " + n.Value + " is ambiguous");
				//						n.Qualifier = s.Alias;
				//					}
				//				}
				//				System.Trace.WriteLine("Use of unqualifed name " + n.Value + " is not efficient. Please use qualified names for maximum performance");
				//throw new Exception("Illegal use of unqualified name " + n.Value + ": all names should be qualified");
			}
			string s = n.Qualifier;
			Correlation correlation;
            if (!this.path.Correlations.TryGetValue(s, out correlation))
				throw new Exception("Unknown correlation " + n.Qualifier);
			string role = n.Value;

			//PropertyData dummy = correlation.EntityData.PropertyInfos[role]; // throws exception if role not found

				
			if( correlation.EntityData.PropertyInfos.ContainsKey(n.Value))
				return;
			if( correlation.EntityData.ReferenceInfos.ContainsKey(n.Value))
			{
				ReferenceData rd = correlation.EntityData.ReferenceInfos[n.Value];
				if(!rd.IsSingleReference && !correlation.NeededRelations.ContainsKey(rd))
				{
					correlation.NeededRelations.Add(rd, n);
				}
				return;
			}

			
			correlation.NeededInverseRelations.Add(n.Value);
			//throw new Exception("property \"" + n.Value + "\" not found in correlation \"" + correlation.Name + "\"");
		}

		public override void VisitCorrelationFilteredNode(ExpressionCorrelationFilteredNode c)
		{
			
		}

	}
}
